using System.Diagnostics;
using System.Data;
using System.Data.Common;
using System;
using System.Collections.Generic;

//namespace AssemblyCSharp
//{
	/// <summary>
	/// Base class for repositories, implemented as a singleton pool.  Concrete repositories
	/// Repositories handle the basic operations of translating between database entities
	/// and domain model objects.  In order to interact with the database, each domain model class must have its own custom repository.
	/// Implementors must provide information about table and column names, must implement a function to map DataRows to objects, and optionally,
	/// a function to map child objects.
	/// </summary>
public class DTORepository<TGameObject>
	where TGameObject : new()
{
	#region fields

	// allItems = cached copy of all DTO objects of a given type
	private List<TGameObject> allItems = new List<TGameObject>();
	
	// currentGameDatabase is the more recent database instance	used to access data
	private GameDatabase currentGameDatabase = null;		
	
	// dblogic must be provided when a type-specific instance is created
	private IDTODbLogic<TGameObject> dbLogic = null;
	
	#endregion fields
	
	#region singleton instance control 
	
	// singleton code based on documentation here: http://msdn.microsoft.com/en-us/library/ff650316.aspx
	
	// instancePool holds all previously instantiated instances of DTORepository
	// keyword "volatile" is used to ensure thread-safe locking
	private static volatile Dictionary<Type, DTORepository<TGameObject>> instancePool = new Dictionary<Type, DTORepository<TGameObject>>();
    private static object syncRoot = new Object();

	// singleton -- ctor must remain private to prevent direct instantiation
	private DTORepository(IDTODbLogic<TGameObject> logicComponent)
	{
        dbLogic = logicComponent; 			
	}

	/// <summary>
	/// Gets an instance of a DTORepository for the game object type specified.
	/// </summary>
	/// <returns>
	/// The repository to be used with objects of that that game object type
	/// </returns>
	/// <param name='logicComponent'>
	/// A class derived from IDTODbLogic, which encapsulates the specific database-object interactions for a given game object type.
	/// </param>
	public static DTORepository<TGameObject> GetInstance(IDTODbLogic<TGameObject> logicComponent)
    {
		Type logicComponentType = logicComponent.GetType();
        if (!instancePool.ContainsKey(logicComponentType))
        {
            lock (syncRoot)
            {
                if (!instancePool.ContainsKey(logicComponentType))
                    instancePool.Add(logicComponentType, new DTORepository<TGameObject>(logicComponent));
            }
        }

        return instancePool[logicComponentType];
    }

	#endregion singleton instance control 		

	#region public methods
	
	/// <summary>
	/// Finds all objects of a given type.
	/// </summary>
	/// <returns>
	/// A List containing all objects found in the game database.
	/// </returns>
	/// <param name='gameDatabase'>
	/// The game database where the data is stored.
	/// </param>
	/// <exception cref='System.ArgumentException'>
	/// An argument exception is thrown is the game database provided is not valid.
	/// </exception>
	public List<TGameObject> FindAll(GameDatabase gameDatabase) {
		Debug.WriteLine("FindAll starting...");

		if (allItems == null)
		{
			allItems = new List<TGameObject>();
		}
		else if (currentGameDatabase == gameDatabase)
		{
			// if we've already retrieved the collection previously from this database, return the cached collection
			return allItems;
		}
		
		DbConnection dbConnection = gameDatabase.CurrentDbConnection;			
		
		try {
			if (dbConnection.State != ConnectionState.Open)
			{
				dbConnection.Open();
			}
		} catch (System.Exception ex) {
			Debug.WriteLine(ex.Message);
			throw;
		}		
		
		// Create a command object which we will use to issue a db query
		DbCommand cmd = dbConnection.CreateCommand();
		cmd.CommandType = CommandType.Text;
		cmd.CommandText = dbLogic.SelectStatement;

		// populate a DataTable with the results of the query using a DataAdapter		
		DbDataAdapter inventoryAdapter = gameDatabase.GetDbDataAdaptor();
		
		if (inventoryAdapter == null)
		{
			throw new System.ArgumentException("Unknown database connection type.", "dbConnection");
		}
		
		DataTable inventoryTable = new DataTable();
		try
		{
			inventoryAdapter.SelectCommand = cmd;
			inventoryAdapter.Fill(inventoryTable);						
		}
		catch (System.Exception ex)
		{
			Debug.WriteLine(ex.Message);
			throw;
		}	
		
		allItems = MapDataTable(inventoryTable, gameDatabase);
		currentGameDatabase = gameDatabase;
				
		// Return the populated collection of game items.
		Debug.WriteLine("LoadInventory returning a collection of " + allItems.Count + " item(s).");
		return allItems;
	} // FindAll
	
	public TGameObject FindByKey(GameDatabase gameDatabase, List<KeyValuePair<string, string>> keyList)
	{
		throw new NotImplementedException("Finding by key not implemented yet.");
	}
	
	#endregion public methods
	
	#region private methods

	private List<TGameObject> MapDataTable (DataTable table, GameDatabase gameDatabase)
	{
		List<TGameObject> allItems = new List<TGameObject>();
		
		// Iterate through the returned datatable and for each row returned, call the dbLogic component
		// to map each row to an object.  For each object, also map any child properties that require their own
		// repository calls.
		foreach (DataRow row in table.Rows)
		{
			TGameObject newItem = dbLogic.CreateShallowObjectFromDataRow(row);
			dbLogic.PopulateChildObjects(newItem, gameDatabase);
			allItems.Add(newItem);
		}
		
		return allItems;
	}

	#endregion private methods

} // class DTORepository

